#include "Avalon/Threading/Async.hpp"

using namespace Avalon;
using namespace Avalon::Threading;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::signals2;
using namespace std;

BaseAsync::~BaseAsync() {}

Async<void>::~Async() {}

BaseAsyncOwner::~BaseAsyncOwner() {}

AsyncOwner<void>::Ptr AsyncOwner<void>::Create() {
  return Ptr(new AsyncOwner());
}

AsyncOwner<void>::Ptr AsyncOwner<void>::CreateComplete() {
  return Ptr(new AsyncOwner(0));
}

AsyncOwner<void>::~AsyncOwner() {}

void AsyncOwner<void>::SetComplete() {
  boost::unique_lock<boost::mutex> lock(m_mutex);
  assert(m_state == PENDING);
  SetState(COMPLETE, lock);
}

void AsyncOwner<void>::SetException(const exception_ptr& e) {
  boost::unique_lock<boost::mutex> lock(m_mutex);
  assert(m_state == PENDING);
  m_exception = e;
  SetState(EXCEPTION, lock);
}

void AsyncOwner<void>::CopyException(const BaseAsyncPtr& async) {
  boost::unique_lock<boost::mutex> lock(m_mutex);
  assert(m_state == PENDING);
  m_exception = async->GetException();
  SetState(EXCEPTION, lock);
}

void AsyncOwner<void>::Get() {
  assert(m_state & (COMPLETE | EXCEPTION));
  if(m_state == EXCEPTION) {
    boost::rethrow_exception(m_exception);
  }
  return;
}

void AsyncOwner<void>::Get(time_duration timeout) {
  if(Wait(timeout) == PENDING) {
    BOOST_THROW_EXCEPTION(TimeoutException());
  }
  return Get();
}

AsyncOwner<void>::State AsyncOwner<void>::GetState() {
  assert(m_state != PENDING);
  return m_state;
}

AsyncOwner<void>::State AsyncOwner<void>::Wait(time_duration timeout) {
  boost::unique_lock<boost::mutex> lock(m_mutex);
  if(m_state == PENDING) {
    if(timeout == pos_infin) {
      m_isFinishedCondition.wait(lock);
      assert(m_state != PENDING);
    } else {
      m_isFinishedCondition.timed_wait(lock, timeout);
    }
  }
  return m_state;
}

connection AsyncOwner<void>::ConnectFinishedSignal(
    const FinishedSignal::slot_type& slot) {
  boost::unique_lock<boost::mutex> lock(m_mutex);
  if(m_state != PENDING) {
    lock.unlock();
    slot();
    return connection();
  }
  return m_finishedSignal->connect(slot);
}

connection AsyncOwner<void>::ConnectFinishedSignal(
    const FinishedSignal::slot_type& slot, State* state) {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  *state = m_state;
  if(m_state != PENDING) {
    return connection();
  }
  return m_finishedSignal->connect(slot);
}

const exception_ptr& AsyncOwner<void>::GetException() {
  assert(m_state == EXCEPTION);
  return m_exception;
}

AsyncOwner<void>::AsyncOwner()
    : m_finishedSignal(new FinishedSignal()) {
  m_state = PENDING;
}

AsyncOwner<void>::AsyncOwner(int dummy) {
  m_state = COMPLETE;
}

void AsyncOwner<void>::SetState(State state,
    boost::unique_lock<boost::mutex>& lock) {
  assert(m_state == PENDING);
  assert(state != PENDING);
  m_state = state;
  m_isFinishedCondition.notify_all();
  lock.unlock();
  (*m_finishedSignal)();
  m_finishedSignal.reset();
}

Async<void>::Ptr Avalon::Threading::NewAsync() {
  return AsyncOwner<void>::CreateComplete();
}
